// Copyright 2022 Jeroen van Nugteren

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// include header
#include "dfrectangle.hh"

#include "dfpolygon.hh"

// code specific to Rat
namespace rat{namespace dm{

	// constructor
	DFRectangle::DFRectangle(){
		set_name("Rectangle");
	}

	// constructcor
	DFRectangle::DFRectangle(
		const fltp x1, const fltp x2, 
		const fltp y1, const fltp y2) : DFRectangle(){
		// set to self
		set_x1(x1); set_x2(x2);
		set_y1(y1); set_y2(y2);
		
		// check validity
		is_valid(true);
	}

	// factory
	ShDFRectanglePr DFRectangle::create(){
		return std::make_shared<DFRectangle>();
	}

	// factory
	ShDFRectanglePr DFRectangle::create(
		const fltp x1, const fltp x2, 
		const fltp y1, const fltp y2){
		return std::make_shared<DFRectangle>(x1,x2,y1,y2);
	}

	// setters
	void DFRectangle::set_x1(const fltp x1){
		x1_ = x1;
	}

	void DFRectangle::set_x2(const fltp x2){
		x2_ = x2;
	}

	void DFRectangle::set_y1(const fltp y1){
		y1_ = y1;
	}

	void DFRectangle::set_y2(const fltp y2){
		y2_ = y2;
	}

	// getters
	fltp DFRectangle::get_x1()const{
		return x1_;
	}

	fltp DFRectangle::get_x2()const{
		return x2_;
	}

	fltp DFRectangle::get_y1()const{
		return y1_;
	}

	fltp DFRectangle::get_y2()const{
		return y2_;
	}

	// perimeter function
	ShPerimeterPr DFRectangle::create_perimeter(const fltp delem) const{
		// get element size
		const arma::uword nelem1 = arma::uword(std::ceil((x2_-x1_)/delem));
		const arma::uword nelem2 = arma::uword(std::ceil((y2_-y1_)/delem));

		// side1 
		const arma::Row<fltp> x1 = arma::linspace<arma::Row<fltp> >(x1_,x2_,nelem1+1);
		const arma::Row<fltp> y1 = arma::linspace<arma::Row<fltp> >(y1_,y1_,nelem1+1);

		// side2 
		const arma::Row<fltp> x2 = arma::linspace<arma::Row<fltp> >(x2_,x2_,nelem2+1);
		const arma::Row<fltp> y2 = arma::linspace<arma::Row<fltp> >(y1_,y2_,nelem2+1);

		// side3 
		const arma::Row<fltp> x3 = arma::linspace<arma::Row<fltp> >(x2_,x1_,nelem1+1);
		const arma::Row<fltp> y3 = arma::linspace<arma::Row<fltp> >(y2_,y2_,nelem1+1);

		// side4 
		const arma::Row<fltp> x4 = arma::linspace<arma::Row<fltp> >(x1_,x1_,nelem2+1);
		const arma::Row<fltp> y4 = arma::linspace<arma::Row<fltp> >(y2_,y1_,nelem2+1);

		// combine
		const arma::Mat<fltp> Rn = arma::join_vert(
			arma::join_horiz(x1.cols(0,nelem1-1),x2.cols(0,nelem2-1),x3.cols(0,nelem1-1),x4.cols(0,nelem2-1)),
			arma::join_horiz(y1.cols(0,nelem1-1),y2.cols(0,nelem2-1),y3.cols(0,nelem1-1),y4.cols(0,nelem2-1)));

		// create element matrix
		const arma::Mat<arma::uword> n=arma::join_vert(
			arma::regspace<arma::Row<arma::uword> >(0,Rn.n_cols-1),
			arma::shift(arma::regspace<arma::Row<arma::uword> >(0,Rn.n_cols-1),-1,1));

	 	// create perimeter
		return Perimeter::create(Rn,n);
	}

	// get bounding box
	arma::Mat<fltp>::fixed<2,2> DFRectangle::get_bounding() const{
		// allocate
		arma::Mat<fltp>::fixed<2,2> Mb;
		
		// bounding in x
		Mb.col(0) = arma::Col<fltp>::fixed<2>{x1_,x2_};
		
		// bounding in y
		Mb.col(1) = arma::Col<fltp>::fixed<2>{y1_,y2_};

		// return box
		return Mb;
	}


	// get fixed points
	arma::Mat<fltp> DFRectangle::get_fixed(const fltp /*abstol*/) const{
		// create list of fixed points
		arma::Mat<fltp> pfix(4,2);
		pfix.row(0) = arma::Row<fltp>{x1_,y1_};
		pfix.row(1) = arma::Row<fltp>{x1_,y2_};
		pfix.row(2) = arma::Row<fltp>{x2_,y2_};
		pfix.row(3) = arma::Row<fltp>{x2_,y1_};
		return pfix;
	}

	// distance function
	arma::Col<fltp> DFRectangle::calc_distance(const arma::Mat<fltp> &p) const{
		return -arma::min(arma::min(arma::min(-y1_+p.col(1),y2_-p.col(1)),-x1_+p.col(0)),x2_-p.col(0));
	}

	// validity check
	bool DFRectangle::is_valid(const bool enable_throws)const{
		// check user input
		if(x2_<=x1_){if(enable_throws){rat_throw_line("x2 must be larger than x1");} return false;};
		if(y2_<=y1_){if(enable_throws){rat_throw_line("y2 must be larger than y1");} return false;};
		return true;
	}

	// type string for serialization
	std::string DFRectangle::get_type(){
		return "rat::dm::dfrectangle";
	}

	// method for serialization into json
	void DFRectangle::serialize(
		Json::Value &js, cmn::SList &list) const{

		// parent
		DistFun::serialize(js,list);

		// store type ID
		js["type"] = get_type();
		
		// properties
		js["x1"] = x1_; 
		js["x2"] = x2_;
		js["y1"] = y1_;	
		js["y2"] = y2_;
	}

	// method for deserialisation from json
	void DFRectangle::deserialize(
		const Json::Value &js, cmn::DSList &list, 
		const cmn::NodeFactoryMap &factory_list, 
		const boost::filesystem::path &pth){

		// parent
		DistFun::deserialize(js,list,factory_list,pth);

		// get values
		set_x1(js["x1"].ASFLTP()); 
		set_x2(js["x2"].ASFLTP());
		set_y1(js["y1"].ASFLTP()); 
		set_y2(js["y2"].ASFLTP());
	}

}}